<?php
declare(strict_types=1);

namespace app\service;

use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;
use think\facade\Log;
use think\facade\Cache;

/**
 * 微信小程序OCR识别服务类
 * 优化版本 - 提供更好的错误处理、缓存机制和代码结构
 */
class OcrService
{
    // OCR识别类型常量
    public const TYPE_ID_CARD = 'idcard';
    public const TYPE_BUSINESS_LICENSE = 'bizlicense';
    public const TYPE_BANK_CARD = 'bankcard';
    public const TYPE_DRIVING_LICENSE = 'drivinglicense';
    public const TYPE_VEHICLE_LICENSE = 'vehiclelicense';

    // API端点配置
    private const API_ENDPOINTS = [
        self::TYPE_ID_CARD => 'https://api.weixin.qq.com/cv/ocr/idcard', // 身份证
        self::TYPE_BUSINESS_LICENSE => 'https://api.weixin.qq.com/cv/ocr/bizlicense', // 营业执照
        self::TYPE_BANK_CARD => 'https://api.weixin.qq.com/cv/ocr/bankcard', // 银行卡
        self::TYPE_DRIVING_LICENSE => 'https://api.weixin.qq.com/cv/ocr/drivinglicense', // 行驶证
        self::TYPE_VEHICLE_LICENSE => 'https://api.weixin.qq.com/cv/ocr/vehiclelicense', // 车辆识别
    ];
    // 错误码映射
    private const ERROR_MESSAGES = [
        40001 => 'access_token无效或已过期',
        40002 => '不合法的凭证类型',
        40003 => '不合法的OpenID',
        45009 => '接口调用超过限额',
        47001 => '解析JSON/XML内容错误',
        48001 => 'api功能未授权',
        85004 => '不支持的媒体文件类型',
        85005 => '不支持的文件类型',
        85006 => '媒体文件为空',
        85007 => '媒体文件大小超过限制',
    ];

    // 支持的图片格式
    private const SUPPORTED_IMAGE_FORMATS = ['jpg', 'jpeg', 'png', 'bmp'];

    private const MAX_SIZE = 10; // 10MB

    // 图片大小限制 (字节)
    private const MAX_IMAGE_SIZE = self::MAX_SIZE * 1024 * 1024;

    private Client $httpClient;
    private int $maxRetries;
    private int $timeout;

    /**
     * 构造函数
     * @param int $maxRetries 最大重试次数
     * @param int $timeout 请求超时时间(秒)
     */
    public function __construct(int $maxRetries = 3, int $timeout = 30)
    {
        $this->httpClient = new Client([
            'timeout' => $timeout,
            'verify' => false, // 在生产环境中建议设置为true
        ]);
        $this->maxRetries = $maxRetries;
        $this->timeout = $timeout;
    }

    /**
     * 身份证OCR识别
     * @param array $params 请求参数
     * @return array 识别结果
     * @throws \Exception
     */
    public function recognizeIdCard(array $params = []): array
    {
        return $this->recognize(self::TYPE_ID_CARD, $params);
    }

    /**
     * 营业执照OCR识别
     * @param array $params 请求参数
     * @return array 识别结果
     * @throws \Exception
     */
    public function recognizeBusinessLicense(array $params = []): array
    {
        return $this->recognize(self::TYPE_BUSINESS_LICENSE, $params);
    }

    /**
     * 银行卡OCR识别
     * @param array $params 请求参数
     * @return array 识别结果
     * @throws \Exception
     */
    public function recognizeBankCard(array $params = []): array
    {
        return $this->recognize(self::TYPE_BANK_CARD, $params);
    }

    /**
     * 驾驶证OCR识别
     * @param array $params 请求参数
     * @return array 识别结果
     * @throws \Exception
     */
    public function recognizeDrivingLicense(array $params = []): array
    {
        return $this->recognize(self::TYPE_DRIVING_LICENSE, $params);
    }

    /**
     * 行驶证OCR识别
     * @param array $params 请求参数
     * @return array 识别结果
     * @throws \Exception
     */
    public function recognizeVehicleLicense(array $params = []): array
    {
        return $this->recognize(self::TYPE_VEHICLE_LICENSE, $params);
    }

    /**
     * 通用OCR识别方法
     * @param string $type 识别类型
     * @param array $params 请求参数
     * @return array 识别结果
     * @throws \Exception
     */
    public function recognize(string $type, array $params = []): array
    {
        // 验证识别类型
        if (!isset(self::API_ENDPOINTS[$type])) {
            throw new \Exception("不支持的OCR识别类型: {$type}");
        }

        // 验证和处理输入参数
        $validatedParams = $this->validateAndProcessParams($params);

        // 执行OCR识别
        return $this->performOcrRecognition($type, $validatedParams);
    }

    /**
     * 验证和处理输入参数
     * @param array $params
     * @return array
     * @throws \Exception
     */
    private function validateAndProcessParams(array $params): array
    {
        // 检查图片参数
        if (empty($params['img_url']) && empty($params['img'])) {
            throw new \Exception('请提供图片URL或图片数据');
        }

        // 如果提供了图片URL，验证URL格式
        if (!empty($params['img_url'])) {
            if (!filter_var($params['img_url'], FILTER_VALIDATE_URL)) {
                throw new \Exception('图片URL格式不正确');
            }

            // 验证图片格式
            $this->validateImageFormat($params['img_url']);
        }

        // 如果提供了图片数据，验证大小
        if (!empty($params['img'])) {
            $imageSize = strlen(base64_decode($params['img']));
            if ($imageSize > self::MAX_IMAGE_SIZE) {
                throw new \Exception('图片大小超过限制(最大' . self::MAX_SIZE . 'MB)');
            }
        }

        return $params;
    }

    /**
     * 验证图片格式
     * @param string $imageUrl
     * @throws \Exception
     */
    private function validateImageFormat(string $imageUrl): void
    {
        $pathInfo = pathinfo(parse_url($imageUrl, PHP_URL_PATH));
        $extension = strtolower($pathInfo['extension'] ?? '');

        if (!in_array($extension, self::SUPPORTED_IMAGE_FORMATS)) {
            throw new \Exception('不支持的图片格式，支持格式: ' . implode(', ', self::SUPPORTED_IMAGE_FORMATS));
        }
    }

    /**
     * 执行OCR识别
     * @param string $type
     * @param array $params
     * @return array
     * @throws \Exception
     */
    private function performOcrRecognition(string $type, array $params): array
    {
        $retryCount = 0;
        $lastException = null;

        while ($retryCount <= $this->maxRetries) {
            try {
                $accessToken = $this->getAccessToken();
                $response = $this->sendOcrRequest($type, $params, $accessToken);
                // 记录成功日志
                logs('OCR识别成功', [
                    'type' => $type,
                    'response' => $response,
                    'retry_count' => $retryCount
                ]);

                return $this->processOcrResponse($response);

            } catch (\Exception $e) {
                $lastException = $e;

                // 如果是token过期错误，刷新token并重试
                if ($this->isTokenExpiredError($e)) {
                    $this->refreshAccessToken();
                    $retryCount++;

                    logs('Access token过期，正在重试', [
                        'type' => $type,
                        'retry_count' => $retryCount
                    ], 'warning');
                    continue;
                }

                // 其他错误直接抛出
                break;
            }
        }

        // 记录失败日志
        logs('OCR识别失败', [
            'type' => $type,
            'retry_count' => $retryCount,
            'error' => $lastException->getMessage()
        ], 'error');
        throw $lastException;
    }

    /**
     * 获取访问令牌（带缓存）
     * @return string
     * @throws \Exception
     */
    private function getAccessToken(): string
    {
        $cacheKey = 'wechat_access_token';
        $token = Cache::get($cacheKey);
        if (empty($token)) {
            $tokenData = app()->WechatMiniProgram->access_token->getToken();
            $token = $tokenData['access_token'] ?? '';
            if (empty($token)) {
                throw new \Exception('获取access_token失败');
            }
            // 缓存token，过期时间比实际过期时间提前5分钟
            $expiresIn = ($tokenData['expires_in'] ?? 7200) - 300;
            Cache::set($cacheKey, $token, $expiresIn);
        }
        return $token;
    }

    /**
     * 刷新访问令牌
     */
    private function refreshAccessToken(): void
    {
        Cache::delete('wechat_access_token');
        app()->WechatMiniProgram->access_token->refresh();
    }

    /**
     * 发送OCR请求
     * @param string $type
     * @param array $params
     * @param string $accessToken
     * @return array
     * @throws \Exception
     */
    private function sendOcrRequest(string $type, array $params, string $accessToken): array
    {
        $url = self::API_ENDPOINTS[$type];
        $requestData = array_merge($params, ['access_token' => $accessToken]);
        try {
            $response = $this->httpClient->post($url, [
                'form_params' => $requestData,
                'headers' => [
                    'Content-Type' => 'application/x-www-form-urlencoded',
                ]
            ]);
            $responseBody = $response->getBody()->getContents();
            $responseData = json_decode($responseBody, true);
            if (json_last_error() !== JSON_ERROR_NONE) {
                throw new \Exception('响应数据JSON解析失败: ' . json_last_error_msg());
            }
            return $responseData;
        } catch (RequestException $e) {
            $errorMessage = '网络请求失败';
            if ($e->hasResponse()) {
                $responseBody = $e->getResponse()->getBody()->getContents();
                $errorData = json_decode($responseBody, true);
                $errorMessage = $errorData['errmsg'] ?? $errorMessage;
            }
            throw new \Exception($errorMessage);
        }
    }

    /**
     * 处理OCR响应
     * @param array $response
     * @return array
     * @throws \Exception
     */
    private function processOcrResponse(array $response): array
    {
        if (isset($response['errcode']) && $response['errcode'] == 42001) {
            throw new \Exception(json_encode($response));
        }

        // 检查错误码
        if (isset($response['errcode']) && $response['errcode'] !== 0) {
            $errorCode = $response['errcode'];
            $errorMessage = self::ERROR_MESSAGES[$errorCode] ?? $response['errmsg'] ?? '未知错误';

//            throw new \Exception("OCR识别失败: {$errorMessage} (错误码: {$errorCode})");
            throw new \Exception("OCR识别失败: 请检查图片后重试");
        }

        // 检查识别结果
        if (!isset($response['errmsg']) || $response['errmsg'] !== 'ok') {
            throw new \Exception('OCR识别失败，请检查图片后重试');
        }

        return $response;
    }

    /**
     * 判断是否为token过期错误
     * @param \Exception $exception
     * @return bool
     */
    private function isTokenExpiredError(\Exception $exception): bool
    {
        $message = $exception->getMessage();
        return strpos($message, '40001') !== false ||
            strpos($message, 'access_token') !== false;
    }

    /**
     * 批量OCR识别
     * @param array $requests 请求列表 [['type' => '', 'params' => []]]
     * @return array
     */
    public function batchRecognize(array $requests): array
    {
        $results = [];

        foreach ($requests as $index => $request) {
            try {
                $results[$index] = $this->recognize(
                    $request['type'] ?? self::TYPE_ID_CARD,
                    $request['params'] ?? []
                );
            } catch (\Exception $e) {
                $results[$index] = [
                    'error' => true,
                    'message' => $e->getMessage()
                ];
            }
        }

        return $results;
    }

    /**
     * 获取支持的OCR类型列表
     * @return array
     */
    public function getSupportedTypes(): array
    {
        return array_keys(self::API_ENDPOINTS);
    }

    /**
     * 获取OCR识别统计信息
     * @param string $date 日期 (Y-m-d)
     * @return array
     */
    public function getOcrStats(string $date = ''): array
    {
        if (empty($date)) {
            $date = date('Y-m-d');
        }

        $cacheKey = "ocr_stats_{$date}";
        $stats = Cache::get($cacheKey, []);

        return [
            'date' => $date,
            'total_requests' => $stats['total'] ?? 0,
            'success_requests' => $stats['success'] ?? 0,
            'failed_requests' => $stats['failed'] ?? 0,
            'success_rate' => $stats['total'] > 0 ? round(($stats['success'] / $stats['total']) * 100, 2) : 0
        ];
    }
}
